import { DomUtils, DomHandler, Parser, } from 'htmlparser2'
import { keys as _keys, some } from 'lodash-es'
import { LangTypes, CompileType } from '../declare'
import { beautifyHtml, getInnerHTML } from '../util'

export type Node = DomHandler['dom'][0]

export type Element = ReturnType<typeof DomUtils.getElementsByTagName>[0]

/**
 * 将 source 转换成 dom 节点树
 * @param {string} source
 * @returns
 */
const make = (source: string) => {
    let handler = new DomHandler()
    let parser = new Parser(handler, {
        lowerCaseAttributeNames: false
    })

    parser.write(source)
    parser.end()
    return handler.dom
}

/**
 * 获得单文件模块dom树
 *
 * @param {*} parentElem
 * @param {string} module
 * @returns
 */
const getSFM = (parentElem: Node, module: string) => {
    let elem = DomUtils.getElementsByTagName(module, parentElem, true)[0]
    let code = ''
    let lang = ''
    let compileType: CompileType | undefined

    if (elem) {
        // code = htmlparser.DomUtils.getInnerHTML(elem)
        code = getInnerHTML(elem)
        lang = elem.attribs.lang

        let langType = LangTypes[`.${lang}`]
        compileType = langType ? langType.compileType : undefined
    }
    return {
        code,
        lang,
        compileType
    }
}

/**
 * 获得单文件组件dom树
 *
 * @param {string} source
 * @returns
 */
const getSFC = (source: string) => {
    let elem = make(source)
    return {
        template: getSFM(elem, 'template'),
        style: getSFM(elem, 'style'),
        script: getSFM(elem, 'script')
    }
}

const combineCode = (templateCode: string, scriptCode: string, styleCode: string) => {
    let code = `
            <template>
              ${templateCode}
            </template>\n
            <script>
              ${scriptCode}
            </script>\n
            <style lang="less">
              ${styleCode}
            </style>`;
    return beautifyHtml(code);
}

const getElementsByAttrRegex = (dom: Node[], nameReg: RegExp, valueReg?: RegExp | undefined) => {
    let elems: Element[] = [];
    dom.forEach((elem: any) => {
        if (elem.type === 'tag' && elem.attribs) {
            let keys = _keys(elem.attribs);
            if (valueReg) {
                if (some(keys, key => nameReg.test(key) && valueReg.test(elem.attribs[key]))) {
                    elems.push(elem);
                }
            }
            else {
                if (some(keys, key => nameReg.test(key))) {
                    elems.push(elem);
                }
            }
        }
        if (elem.children) {
            elems = elems.concat(getElementsByAttrRegex(elem.children, nameReg, valueReg));
        }
    });
    return elems;
}

const getElementsByTagName = (dom: Node[], tagNames: string | string[]) => {
    if (typeof tagNames === 'string') {
        tagNames = [tagNames];
    }
    return DomUtils.getElementsByTagName((name: string) => {
        return tagNames.indexOf(name) > -1;
    }, dom, true);
}

const getTagNamesByRegex = (dom: Node[], tagReg: RegExp, duplicate?: Boolean | undefined) => {
    let tags: string[] = [];
    dom.forEach((elem: any) => {
        if (elem.type === 'tag' && tagReg.test(elem.name)) {
            if (duplicate) {
                tags.push(elem.name);
            }
            else {
                if (tags.indexOf(elem.name) < 0) {
                    tags.push(elem.name);
                }
            }
        }
        if (elem.children) {
            tags = tags.concat(getTagNamesByRegex(elem.children, tagReg));
        }
    });
    return tags;
}

const isOneRootDom = (dom: any) => {
    let tagCount = dom.filter((elem: any) => {
        return elem.type === 'tag';
    }).length;
    return tagCount === 1;
}

export const dom = {
    make,
    getSFM,
    getSFC,
    combineCode,
    getElementsByAttrRegex,
    getElementsByTagName,
    getTagNamesByRegex,
    isOneRootDom
}
